﻿using System;
using JetBrains.Annotations;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Interfaces.Views;
using MugenMvvmToolkit.Utils;
using MugenMvvmToolkit.ViewModels;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     Represents the default implemenation of <see cref="IViewManager" />
    /// </summary>
    public class ViewManager : IViewManager
    {
        #region Fields

        private readonly Func<IInitializationContext, bool> _canInitializeViewDelegate;
        private readonly Func<IViewModel, string, IView> _getViewDelegate;
        private readonly IViewMappingProvider _viewMappingProvider;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewManager"/> class.
        /// </summary>
        public ViewManager([NotNull]Func<IInitializationContext, bool> canInitializeViewDelegate, [NotNull] Func<IViewModel, string, IView> getViewDelegate)
        {
            Should.NotBeNull(canInitializeViewDelegate, "canInitializeViewDelegate");
            Should.NotBeNull(getViewDelegate, "getViewDelegate");
            _canInitializeViewDelegate = canInitializeViewDelegate;
            _getViewDelegate = getViewDelegate;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewManager"/> class.
        /// </summary>
        public ViewManager([NotNull]Func<IInitializationContext, bool> canInitializeViewDelegate, [NotNull] IViewMappingProvider viewMappingProvider)
        {
            Should.NotBeNull(canInitializeViewDelegate, "canInitializeViewDelegate");
            _canInitializeViewDelegate = canInitializeViewDelegate;
            _viewMappingProvider = viewMappingProvider;
        }

        #endregion

        #region Implementation of IViewFactoryProvider

        /// <summary>
        ///     Gets an instance of <see cref="IView" /> for the specified view model.
        /// </summary>
        /// <param name="viewModel">The view model which is now initialized.</param>
        /// <param name="viewBindingName">The view binding name, if any.</param>
        /// <returns>
        ///     An instance of <see cref="IView" />.
        /// </returns>
        public virtual IView GetView(IViewModel viewModel, string viewBindingName = null)
        {
            Should.NotBeNull(viewModel, "viewModel");
            if (_getViewDelegate != null)
                return _getViewDelegate(viewModel, viewBindingName);
            viewModel.IocAdapter.NotBeDisposed();
            Type viewType = viewModel.GetType();
            IViewMappingItem mappingItem = _viewMappingProvider.FindMappingForViewModel(viewType, viewBindingName, true);
            return (IView)viewModel.IocAdapter.Get(mappingItem.ViewType);
        }

        /// <summary>
        ///     Configures the specified view for the specified view-model.
        /// </summary>
        /// <param name="initializationContext">
        ///     The specified <see cref="IInitializationContext" />.
        /// </param>
        public virtual bool InitializeView(IInitializationContext initializationContext)
        {
            Should.NotBeNull(initializationContext, "initializationContext");
            Should.NotBeNull(initializationContext.ViewModel, "initializationContext.ViewModel");
            if (!_canInitializeViewDelegate(initializationContext))
                return false;
            var thrMan = GetThreadManager(initializationContext.ViewModel.IocAdapter);
            thrMan.InvokeInUi(() => InitializeView(initializationContext.ViewModel, GetView(initializationContext.ViewModel, initializationContext.ActivationPolicy.ViewBindingName)));
            return true;
        }

        /// <summary>
        ///     Configures the specified view for the specified view-model.
        /// </summary>
        /// <param name="viewModel">The specified view model.</param>
        /// <param name="view">The specified view.</param>
        public virtual void InitializeView(IViewModel viewModel, IView view)
        {
            Should.NotBeNull(viewModel, "viewModel");
            Should.NotBeNull(view, "view");
            var thrMan = GetThreadManager(viewModel.IocAdapter);
            thrMan.InvokeInUi(() =>
                {
                    var hasView = viewModel as IHasView;
                    if (hasView != null)
                    {
                        if (hasView.View != null)
                        {
                            if (ReferenceEquals(hasView.View, view))
                                return;
                            CleanupView(hasView, hasView.View);
                        }
                        InitializeViewInternal(viewModel, view);
                        hasView.View = view;
                        return;
                    }

                    var viewProperty = MvvmUtilsInternal.GetViewProperty(viewModel.GetType());
                    if (viewProperty != null)
                    {
                        var oldView = (IView)viewProperty.GetValue(viewModel, null);
                        if (oldView != null)
                        {
                            if (ReferenceEquals(oldView, view))
                                return;
                            CleanupView(viewModel, oldView);
                        }
                        InitializeViewInternal(viewModel, view);
                        viewProperty.SetValue(viewModel, view, null);
                        return;
                    }

                    InitializeViewInternal(viewModel, view);
                });
        }

        /// <summary>
        ///     Clears view in the specified view-model
        /// </summary>
        /// <param name="viewModel">The specified view model.</param>
        public virtual void CleanupView(IViewModel viewModel)
        {
            Should.NotBeNull(viewModel, "viewModel");
            var hasView = viewModel as IHasView;
            if (hasView != null && hasView.View != null)
                CleanupView(hasView, hasView.View);
        }

        /// <summary>
        ///     Clears view in the specified view-model
        /// </summary>
        /// <param name="viewModel">The specified view model.</param>
        /// <param name="view">The specified view.</param>
        public virtual void CleanupView(IViewModel viewModel, IView view)
        {
            Should.NotBeNull(viewModel, "viewModel");
            Should.NotBeNull(view, "view");
            var thrMan = GetThreadManager(viewModel.IocAdapter);
            thrMan.InvokeInUi(() =>
                                  {
                                      var hasView = viewModel as IHasView;
                                      if (hasView != null && hasView.View != null)
                                          hasView.View = null;

                                      var viewProperty = MvvmUtilsInternal.GetViewProperty(viewModel.GetType());
                                      if (viewProperty != null && viewProperty.GetValue(viewModel, null) != null)
                                          viewProperty.SetValue(viewModel, null, null);

                                      CleanupViewInternal(view);
                                  });
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Configures the specified view to the specified view-model.
        /// </summary>
        private static void InitializeViewInternal(IViewModel viewModel, IView view)
        {
            if (view == null) return;
            view.DataContext = viewModel;
            var property = MvvmUtilsInternal.GetViewModelProperty(view.GetType());
            if (property == null) return;
            property.SetValue(view, viewModel, null);
        }

        /// <summary>
        /// Clears view in the specified view-model
        /// </summary>
        /// <param name="view">The specified view.</param>
        private static void CleanupViewInternal(IView view)
        {
            InitializeViewInternal(null, view);
        }

        private static IThreadManager GetThreadManager(IIocAdapter iocAdapter)
        {
            if (iocAdapter == null || iocAdapter.IsDisposed)
                return GlobalSettings.DefaultThreadManager;
            return iocAdapter.Get<IThreadManager>();
        }

        #endregion
    }
}